#include "my_mem.h"

static size_t alignment = MY_MEM_4K_ALIGNMENT;

#if (MY_MEM_NO_POOL)

void
my_mem_init(int64_t water_level)
{
    // nothing
    (void)water_level;
}

void *
my_mem_alloc(size_t size)
{
    my_mem_item_head_t          *item;

    if (size > MY_MEM_MAX_ALLOC_SIZE) {
        assert(!"to big alloc size");
    }

    item = malloc(sizeof(my_mem_item_head_t) + size);
    item->magic = MY_MEM_ITEM_MAGIC;
    item->size = (uint32_t)size;
    item->next = NULL;

    return (void *)(item) + sizeof(my_mem_item_head_t);
}

void
my_mem_free(void *ptr)
{
    my_mem_item_head_t          *item = NULL;
    item = ptr - sizeof(my_mem_item_head_t);
    assert(item->magic == MY_MEM_ITEM_MAGIC);
    free(item);
}


void
my_mem_status_report(char *buf, size_t buf_size)
{
    snprintf(buf, buf_size, "no pool\n");
}

void
my_mem_clean()
{
    // nothing
}


void
my_4kaligned_mem_init(int64_t water_level)
{
    // nothing
    (void)water_level;
}


void *
my_4kaligned_mem_alloc(size_t size)
{
    if (size > MY_MEM_MAX_ALLOC_SIZE) {
        assert(!"to big alloc size");
    }

    size_t offset = (alignment - 1) + sizeof(my_aligned_mem_item_head_t);
    my_aligned_mem_item_head_t *item = malloc(size + offset);
    item->magic = MY_ALIGNED_MEM_ITEM_MAGIC;
    item->next = item->ptr = NULL;
    item->size = (uint32_t)size;
    void *p1 = (void *)item;
    void **p2 = (void**)(((size_t)p1 + offset) & ~(alignment - 1));
    p2[-1] = p1;
    return p2;
}


void
my_4kaligned_mem_free(void *ptr)
{
    void                            *p1;
    my_aligned_mem_item_head_t      *item;

    p1 = ((void **)ptr)[-1];
    item = (my_aligned_mem_item_head_t *)p1;
    assert(item->magic == MY_ALIGNED_MEM_ITEM_MAGIC);
    free(item);
}


void
my_4kaligned_mem_status_report(char *buf, size_t buf_size)
{
    snprintf(buf, buf_size, "no pool\n");
}


void
my_4kaligned_mem_clean()
{
    // nothing
}


#else

static my_mem_freelists_t       g_mem_freelists;

void
my_mem_init(int64_t water_level)
{
    int                 i;
    for (i = 0; i < MEM_FREELIST_MODE_END; i++) {
        my_spinlock_init(&g_mem_freelists.lists[i].lock);
        g_mem_freelists.lists[i].space_size
            = my_mem_freelist_mode_to_size(i);
        g_mem_freelists.lists[i].water_level = water_level;
        g_mem_freelists.lists[i].count = 0;
        g_mem_freelists.lists[i].used = 0;
        g_mem_freelists.lists[i].head = (void *)(-1);
    }

    g_mem_freelists.inited = MY_TRUE;
}


static inline my_mem_item_head_t *
my_mem_base_alloc(size_t size)
{
    if (size > MY_MEM_MAX_ALLOC_SIZE) {
        assert(!"to big alloc size");
    }

    my_mem_item_head_t      *item;
    item = malloc(sizeof(my_mem_item_head_t) + size);
    item->magic = MY_MEM_ITEM_MAGIC;
    item->size = (uint32_t)size;
    item->next = NULL;

    return item;
}

static inline void
my_mem_base_free(my_mem_item_head_t *item)
{
    free(item);
}


void *
my_mem_alloc(size_t size)
{
    if (size > MY_MEM_MAX_ALLOC_SIZE) {
        assert(!"to big alloc size");
    }

    int                     i;
    int                     mode = -1;
    size_t                  mode_size;

    my_mem_item_head_t      *item = NULL;
    my_mem_freelist_t       *freelist = NULL;

    for (i = 0; i < MEM_FREELIST_MODE_END; i++) {
        mode_size = (size_t)my_mem_freelist_mode_to_size(i);
        if (size <= mode_size) {
            size = mode_size;
            mode = i;
            break;
        }
    }

    if (mode != -1 && g_mem_freelists.inited) {
        freelist = &g_mem_freelists.lists[mode];
    }

    if (freelist != NULL) {
        my_spinlock_lock(&freelist->lock);
        if (freelist->count > 0) {
            item = freelist->head;
            freelist->head = item->next;
            item->next = NULL;
            freelist->count--;
        }
        freelist->used++;
        my_spinlock_unlock(&freelist->lock);
    }

    if (item == NULL) {
        item = my_mem_base_alloc(size);
    }

    return (void *)(item) + sizeof(my_mem_item_head_t);
}


void
my_mem_free(void *ptr)
{
    my_mem_item_head_t      *item = NULL;
    my_mem_freelist_t       *freelist = NULL;
    int                     mode = -1;
    int                     i;

    item = ptr - sizeof(my_mem_item_head_t);
    assert(item->magic == MY_MEM_ITEM_MAGIC);
    if (item->next != NULL) {
        assert(!"double free or mem stampede");
    }

    for (i = 0; i < MEM_FREELIST_MODE_END; i++) {
        if (item->size == my_mem_freelist_mode_to_size(i)) {
            mode = i;
            break;
        }
    }

    if (mode != -1 && g_mem_freelists.inited) {
        freelist = &g_mem_freelists.lists[mode];
    }

    if (freelist != NULL) {
        my_spinlock_lock(&freelist->lock);
        if (freelist->count < freelist->water_level) {
            item->next = freelist->head;
            freelist->head = item;
            item = NULL;
            freelist->count++;
        }
        freelist->used--;
        my_spinlock_unlock(&freelist->lock);
    }

    if (item != NULL) {
        my_mem_base_free(item);
    }
}


void
my_mem_status_report(char *buf, size_t buf_size)
{
    if (!g_mem_freelists.inited) {
        snprintf(buf, buf_size, "no inited\n");
        return;
    }

    int                 off = 0;
    int                 i;
    int64_t             size;
    int64_t             count;
    int64_t             used;
    my_mem_freelist_t   *freelist = NULL;
    for (i = 0; i < MEM_FREELIST_MODE_END; i++) {
        freelist = &g_mem_freelists.lists[i];
        my_spinlock_lock(&freelist->lock);
        size = freelist->space_size;
        count = freelist->count;
        used = freelist->used;
        my_spinlock_unlock(&freelist->lock);

        off += snprintf(buf + off, buf_size - (size_t)off,
            "%ld mode, count: %ld, used: %ld\n", size, count, used);
    }
}


static void
my_mem_freelist_clean(my_mem_freelist_t *freelist)
{
    my_bool_t           exit = MY_FALSE;
    my_mem_item_head_t  *item;

    while (1) {
        item = NULL;
        my_spinlock_lock(&freelist->lock);
        if (freelist->count > 0) {
            item = freelist->head;
            freelist->head = item->next;
            item->next = NULL;
            freelist->count--;
        } else {
            exit = MY_TRUE;
        }
        my_spinlock_unlock(&freelist->lock);

        if (item != NULL) {
            my_mem_base_free(item);
        }

        if (exit) {
            break;
        }
    }
}


void
my_mem_clean()
{
    if (!g_mem_freelists.inited) {
        return;
    }

    int                 i;

    for (i = 0; i < MEM_FREELIST_MODE_END; i++) {
        my_mem_freelist_clean(&g_mem_freelists.lists[i]);
    }
}


static my_4kaligned_mem_freelists_t     g_4kaligned_mem_freelists;

void
my_4kaligned_mem_init(int64_t water_level)
{
    int                         i;
    for (i = 0; i < MEM_4KALIGNED_FREELIST_MODE_END; i++) {
        my_spinlock_init(&g_4kaligned_mem_freelists.lists[i].lock);
        g_4kaligned_mem_freelists.lists[i].space_size
            = my_4kaligned_mem_freelist_mode_to_size(i);
        g_4kaligned_mem_freelists.lists[i].water_level = water_level;
        g_4kaligned_mem_freelists.lists[i].count = 0;
        g_4kaligned_mem_freelists.lists[i].used = 0;
        g_4kaligned_mem_freelists.lists[i].head = (void *)(-1);
    }

    g_4kaligned_mem_freelists.inited = MY_TRUE;
}


static inline my_aligned_mem_item_head_t *
my_4kaligned_mem_base_alloc(size_t size)
{
    if (size > MY_MEM_MAX_ALLOC_SIZE) {
        assert(!"to big alloc size");
    }

    size_t offset = (alignment - 1) + sizeof(my_aligned_mem_item_head_t);
    my_aligned_mem_item_head_t *item = malloc(size + offset);
    item->magic = MY_ALIGNED_MEM_ITEM_MAGIC;
    item->next = item->ptr = NULL;
    item->size = (uint32_t)size;
    void *p1 = (void *)item;
    void **p2 = (void**)(((size_t)p1 + offset) & ~(alignment - 1));
    p2[-1] = p1;
    return item;
}


static inline void
my_4kaligned_mem_base_free(my_aligned_mem_item_head_t *item)
{
    free(item);
}


void *
my_4kaligned_mem_alloc(size_t size)
{
    if (size > MY_MEM_MAX_ALLOC_SIZE) {
        assert(!"to big alloc size");
    }

    my_mem_freelist_t           *freelist = NULL;
    my_aligned_mem_item_head_t  *item = NULL;
    int                         i;
    int                         mode = -1;
    size_t                      mode_size;

    for (i = 0; i < MEM_4KALIGNED_FREELIST_MODE_END; i++) {
        mode_size = (size_t)my_4kaligned_mem_freelist_mode_to_size(i);
        if (size <= mode_size) {
            size = mode_size;
            mode = i;
            break;
        }
    }

    if (mode != -1 && g_4kaligned_mem_freelists.inited) {
        freelist = &g_4kaligned_mem_freelists.lists[mode];
    }

    if (freelist != NULL) {
        my_spinlock_lock(&freelist->lock);
        if (freelist->count > 0) {
            item = freelist->head;
            freelist->head = item->next;
            item->next = NULL;
            freelist->count--;
        }
        freelist->used++;
        my_spinlock_unlock(&freelist->lock);
    }

    if (item == NULL) {
        item = my_4kaligned_mem_base_alloc(size);
    }

    return MY_4KALIGNED_MEM_ITEM_PTR(item);
}


void
my_4kaligned_mem_free(void *ptr)
{
    void                            *p1;
    my_aligned_mem_item_head_t      *item;
    my_mem_freelist_t               *freelist = NULL;
    int                             mode = -1;
    int                             i;

    p1 = ((void **)ptr)[-1];
    item = (my_aligned_mem_item_head_t *)p1;
    assert(item->magic == MY_ALIGNED_MEM_ITEM_MAGIC);
    if (item->next != NULL) {
        assert(!"double free or mem stampede");
    }

    for (i = 0; i < MEM_4KALIGNED_FREELIST_MODE_END; i++) {
        if (item->size == my_4kaligned_mem_freelist_mode_to_size(i)) {
            mode = i;
            break;
        }
    }

    if (mode != -1 && g_4kaligned_mem_freelists.inited) {
        freelist = &g_4kaligned_mem_freelists.lists[mode];
    }

    if (freelist != NULL) {
        my_spinlock_lock(&freelist->lock);
        if (freelist->count < freelist->water_level) {
            item->next = freelist->head;
            freelist->head = item;
            item = NULL;
            freelist->count++;
        }
        freelist->used--;
        my_spinlock_unlock(&freelist->lock);
    }

    if (item != NULL) {
        my_4kaligned_mem_base_free(item);
    }
}


void
my_4kaligned_mem_status_report(char *buf, size_t buf_size)
{
    if (!g_4kaligned_mem_freelists.inited) {
        snprintf(buf, buf_size, "no inited \n");
        return;
    }

    int                     off = 0;
    int                     i;
    int64_t                 size;
    int64_t                 count;
    int64_t                 used;
    my_mem_freelist_t       *freelist = NULL;
    for (i = 0; i < MEM_4KALIGNED_FREELIST_MODE_END; i++) {
        freelist = &g_4kaligned_mem_freelists.lists[i];
        my_spinlock_lock(&freelist->lock);
        size = freelist->space_size;
        count = freelist->count;
        used = freelist->used;
        my_spinlock_unlock(&freelist->lock);

        off += snprintf(buf + off, buf_size - (size_t)off,
            "%ld aligned mode, count: %ld, used: %ld\n", size, count, used);
    }
}


static void
my_4kaligned_mem_freelist_clean(my_mem_freelist_t *freelist)
{
    my_bool_t                       exit = MY_FALSE;
    my_aligned_mem_item_head_t      *item;

    while (1) {
        item = NULL;
        my_spinlock_lock(&freelist->lock);
        if (freelist->count > 0) {
            item = freelist->head;
            freelist->head = item->next;
            item->next = NULL;
            freelist->count--;
        } else {
            exit = MY_TRUE;
        }
        my_spinlock_unlock(&freelist->lock);

        if (item != NULL) {
            my_4kaligned_mem_base_free(item);
        }

        if (exit) {
            break;
        }
    }
}


void
my_4kaligned_mem_clean()
{
    if (!g_4kaligned_mem_freelists.inited) {
        return;
    }

    int                             i;

    for (i = 0; i < MEM_4KALIGNED_FREELIST_MODE_END; i++) {
        my_4kaligned_mem_freelist_clean(&g_4kaligned_mem_freelists.lists[i]);
    }
}

#endif